package patternmining.model.construction;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/** Container class for the members of ONE type declared in a java source file.
 *  Maps attributs(identified by it's name) and method's (identified by name and paramter type sequence)
 *  to its properties like type, return-type and visability
 * 
 * @version 1.0, 15.07.2008
 * @author Holger Wiehen
 */
class MemberTable {

    private Map<String, AttributeData> attributes;
    private Map<String, OverloadedMethodVariants> methods;
    
    private String nameOfOwningType;
    
    public MemberTable(String nameOfOwningType){
        this.nameOfOwningType = nameOfOwningType;
        this.attributes = new HashMap<String, AttributeData>();
        this.methods = new HashMap<String, OverloadedMethodVariants>();
    }
    
    /**Adds to the members, considered for the type associated to this MemberTable
     * an attribute with it's properties name, type, and set of modifiers.
     * Modifiers considered are: static, private, protected, default, public and final 
     * 
     * @param name
     * @param type
     * @param modifiers a Set of Modifier constants 
     */
    public void addAttribute(String name, String type, Set<Modifier> modifiers) throws MemberCollisionException{
        if( attributes.containsKey(name) ) throw new MemberCollisionException();
        attributes.put( name, new AttributeData(name, type, modifiers) );
    }
    
    /**Adds to the members, considered for the type associated to this MemberTable
     * a method with it's properties name, returnType, sequence of parameter types, and set of modifiers.
     * Modifiers considered are: static, private, protected, default, public and final 
     * 
     * @param name
     * @param returnType
     * @param parameterTypes
     * @param modifiers
     */
    public void addMethod(String name, String returnType, List<String> parameterTypes, Set<Modifier> modifiers) throws MemberCollisionException{
        if( methods.containsKey(name) && methods.get(name).containsVariant(parameterTypes) )    throw new MemberCollisionException();
        OverloadedMethodVariants variants;
        if( ! methods.containsKey(name) ){
            variants = new OverloadedMethodVariants(name);
            methods.put(name, variants);
        }
        else{
            variants = methods.get(name);
        }
        variants.addVariation(parameterTypes, returnType, modifiers);
    }
    
    public String getAttributeType(String name) throws UnknownMemberException{
        if ( ! attributes.containsKey(name) ) throw new UnknownMemberException();
        return attributes.get(name).type;
    }
    
    public String getMethodReturnType(String name, List<String> parametersTypes)throws UnknownMemberException{
        if ( ! methods.containsKey(name) )      throw new UnknownMemberException();
        return methods.get(name).getReturnType(parametersTypes);
    }
    
    public String getUniqueMethodName(String name, List<String> parameterTypes) throws UnknownMemberException{
        if ( ! methods.containsKey(name) )      throw new UnknownMemberException();
        return methods.get(name).getUniqueName(parameterTypes);
    }
    
    /**Inner class that encapsulates the data associated to an attribute
     * 
     */
    private class AttributeData{
        String name;
        String type;
        Set<Modifier> modifiers;
        AttributeData(String name, String type, Set<Modifier> modifiers){
            this.name = name;
            this.type = type;
            this.modifiers = modifiers;
        }
    }
    
    /**Inner class that manages the generation of unique names for overloaded method names and that
     * returns the unique method name, given the simple name and the list of parameter types 
     */
    private class OverloadedMethodVariants{
        
        private String name;
        private int uniqueSuffix;
        private int initialSuffix = 0;
        private Map<List<String>,MethodData> variants;
        
        /**
         * 
         * @param name , the simple name of the method ex. "containsVariant" and not the qualified name "MethodNameVariants.containsVariant"
         */
        OverloadedMethodVariants( String name ){
            this.name = name;
            this.uniqueSuffix = initialSuffix;
            variants = new HashMap<List<String>,MethodData>();
        }
        
        boolean containsVariant(List<String> parameterTypes){
            return variants.containsKey(parameterTypes);
        }
        
        /**A variant of a overloaded method is identified by the unique sequence of parameter types
         * 
         * @param parameterTypes
         * @param returnType
         * @param modifiers
         */
        void addVariation(List<String> parameterTypes, String returnType, Set<Modifier> modifiers){
            String uniqueName;
            if( uniqueSuffix == initialSuffix ) uniqueName = name;    
            else                                uniqueName = name + "_" + uniqueSuffix;
            MethodData mData = new MethodData(uniqueName, returnType, parameterTypes, modifiers);
            variants.put(parameterTypes, mData);
            uniqueSuffix++;
        }
        
        String getUniqueName(List<String> parameterTypes) throws UnknownMemberException{
            if( ! variants.containsKey(parameterTypes) ){
                throw new UnknownMemberException();
            }
            return variants.get(parameterTypes).uniqueName;
        }
        
        String getReturnType(List<String> parameterTypes) throws UnknownMemberException{
            if( ! variants.containsKey(parameterTypes) ){
                throw new UnknownMemberException();
            }
            return variants.get(parameterTypes).returnType;
        }
        
        /**Encapsulation class to tie together the attributes of a method definition
         * 
         */
        private class MethodData{
            String uniqueName;
            String returnType;
            List<String> parameterTypes;
            Set<Modifier> modifiers;            
            MethodData(String uniqueName, String returnType, List<String> parameterTypes, Set<Modifier> modifiers){
                this.uniqueName = uniqueName;
                this.returnType = returnType;
                this.parameterTypes = parameterTypes;
                this.modifiers = modifiers;
            }        
        }
    
    }
   
}

/**Exeption thrown if a MemberTable instance is queried to return the data of a member (attribute or method) not defined in the
 * type associated to the table.
 * 
 * @author Holger
 */
class UnknownMemberException extends Exception {
    
}

/**Exeption thrown if a MemberTable instance is queried to include a member, who's identifier is already used by a registered member
 * For attributes the unique identifier is the name. For methods the unique identifier is the name combined whith the parameter type sequence.
 * @author Holger
 */
class MemberCollisionException extends Exception {
    
}

/**Possible constant values of modifieres considered. Parsing a java source may identify more
 * modifier values (see the java grammar for that) but they are deregarded, because there semantics
 * won't be used in the construction of the model equivalent to the java sources analiezed
 * 
 * @author Holger
 */
enum Modifier{
    DEFAULT, PROTECTED, PRIVATE, PUBLIC, STATIC, FINAL, ABSTRACT, NATIVE, SYNCHRONIZED, TRANSIENT, VOLATILE, STRICTFP, ANNOTATION
}


        
   